#include "server.h"

static int get_gcmd_name(char* name, const char* gcmd)
{
    int     p = 0;
    while((*(gcmd + p) != ' ') && (*(gcmd + p) != '\0')){
        *(name + p) = *(gcmd + p);
        p++;
    }
    *(name + p) = '\0';
    return p;
}

static int send_gvim_message(int accept_socket, const char *line_buff )
{
    //         -1 : 发送失败
    //          0 : socket已经断开
    // 1 ~ size-1 : 正常
    //
    char    message[1024];
    strcpy(message, line_buff);
    strcat(message, "\n");
    send(accept_socket, (void*)message, strlen(message), 0);

}
static int get_gvim_message(int accept_socket, char *line_buff, int size )
{
    // 急需修改, 现在只能勉强运行
    //         -1 : 读取出错
    //          0 : socket已经断开
    // 1 ~ size-1 : 正常读取
    //       size : 读满
    char   *line_ptr;
    int     recv_len;

    line_ptr = line_buff;
    while((recv_len = recv(accept_socket, (void*)line_ptr, 1, 0))>=0){
        // SOCKET_ERROR
        // 0 shutdown
        // length
        if(recv_len == 0){
            return 0;
        }else if(recv_len < 0){
            return -1;
        }else{
            if((*line_ptr == '\n') || (*line_ptr == '\0')){
                *line_ptr = '\0';
                break;
            }
            line_ptr++;
        }
    }
    return 1;
}

static int get_gdb_output_line(int fd, char *line_buff, int size )
{
    // 急需修改, 现在只能勉强运行
    //         -1 : 读取出错
    //          0 : fd已经关闭
    // 1 ~ size-1 : 正常读取
    //       size : 读满
    char   *line_ptr;
    int     ret_len;

    line_ptr =  line_buff;
    while((ret_len = read(fd, line_ptr, 1))>=0){
        if(ret_len < 0){
            return -1;
        }else if(ret_len == 0){
            return 0;
        }else{
            if((*line_ptr == '\n') || (*line_ptr == '\0')){
                *line_ptr = '\0';
                break;
            }
            line_ptr++;
        }
    }
    return 1;
}

static int get_gdb_response(vgdb_server *server)
{
    // 0 : 无输出
    //>0 : 输出行数
    //<0 : 输出出错
    // 返回gdb输出的行数, 从1开始计数
    // 不包括\x1a\x1apre-prompt那一行, 那一行被丢弃
    int         rwfd;
    int         mline_len;
    int         mline_nr;
    int         ret_col;
    char       *mline_ptr;

    mline_ptr = server->mline_buff;
    mline_len = server->mline_blen;
    mline_nr  = server->mline_bnr;
    rwfd      = server->gdb_rwfd;
    ret_col   = 0;

    while(1){
        mline_ptr += mline_len;
        get_gdb_output_line(rwfd, mline_ptr, mline_len);
        if(!strcmp(mline_ptr, "\x1a\x1apre-prompt")){
            // 这里可以需要去掉一个空行
            // 在pre-prompt之前有一个空行
            ret_col--;
            break;
        }
        ret_col++;
    }
    server->mline_cols = ret_col;
    return ret_col;
}


static int get_gdb_init_response(vgdb_server *server)
{
    int         rwfd;
    int         mline_len;
    char       *mline_ptr;

    mline_ptr = server->mline_buff;
    mline_len = server->mline_blen;
    rwfd      = server->gdb_rwfd;

    // mline第一行被当做临时缓冲
    while(1){
        get_gdb_output_line(rwfd, mline_ptr, mline_len);
        if(!strcmp(mline_ptr, "\x1a\x1aprompt")){
            break;
        }
    }
    return 0;
}

static int exec_gdb_cmd(vgdb_server *server, const char *gcmd)
{
    // <0 : 命令发生错误
    //  0 : 退出gdb
    // >0 : 继续可以继续下一条命令
    int         rwfd;
    int         mline_len;
    int         mline_bnr;
    char       *mline_ptr;
    char        gcmd_name[128];
    int         i;

    mline_ptr = server->mline_buff;
    mline_len = server->mline_blen;
    mline_bnr  = server->mline_bnr;
    rwfd      = server->gdb_rwfd;

    strcpy(mline_ptr, gcmd);
    strcat(mline_ptr, "\n");
    write(rwfd, mline_ptr, strlen(mline_ptr));

    while(1){
        // 读取post-prompt
        // 有时候会把multi_line_buff[0]做成临时缓冲区
        get_gdb_output_line(rwfd, mline_ptr, mline_len);
        if(!strcmp(mline_ptr, "\x1a\x1apost-prompt")){
            break;
        }
    }

    // 命令保存在[0]中, 没有回车
    strcpy(mline_ptr, gcmd);
    get_gcmd_name(gcmd_name, mline_ptr);
    if(!strcmp(gcmd_name, "quit") || !strcmp(gcmd_name, "q")){
        // quit命令输入后没有任何的response
        waitpid(server->gdb_pid, NULL, 0);
        server->gdb_state = VGDB_GDB_QUIT;
        return 0;
    } else {
        // 这里会获得"\x1a\x1a pre-prompt"
        // 作为应答停止的标志位
        get_gdb_response(server);

        if (!strcmp(gcmd_name, "file")) {

        } else if (!strcmp(gcmd_name, "list")) {
        }
        /* 这里使用mline的信息
        if (!strcmp(gcmd_name, "file")) {
        } else if (!strcmp(gcmd_name, "list")) {
        }
        */

        for(i=0; i <= server->mline_cols; i++){
            printf("i = %2d|%s\n", i, server->mline_buff + i*server->mline_blen);
        }

        while(1){
            // 读取"\x1a\x1a prempt"
            // 这个将在下一个命令输入之前出现
            get_gdb_output_line(rwfd, mline_ptr, mline_len);
            if(!strcmp(mline_ptr, "\x1a\x1aprompt")){
                break;
            }
        }
    }
    return 1;
}

static int connect_to_gvim(vgdb_server *server)
{    
    char   *hostname   =   "localhost";
    char   *password   =   "changeme";
    int     port       =   3219;

    int     listen_socket;
    int     accept_socket;

    struct sockaddr_in  socket_addr;
    struct hostent     *host;
    socklen_t           socket_len;

    if ((listen_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket() failed");
        return 1;
    }

    memset((void *)&socket_addr, 0, sizeof(socket_addr));
    socket_addr.sin_family = AF_INET;
    socket_addr.sin_port = htons(port);
    if ((host = gethostbyname(hostname)) == NULL) {
        close( listen_socket );
        perror("gethostbyname() failed");
        return 1;
    }
    memcpy((void *)&socket_addr.sin_addr, host->h_addr, (size_t)host->h_length);

    if (bind(listen_socket, (struct sockaddr *)&socket_addr, sizeof(socket_addr)) == -1) {
        close( listen_socket );
        perror("bind() failed");
        return 1;
    }

    if (listen(listen_socket, 5) == -1 ) {
        close( listen_socket );
        perror("listen() failed");
        return 1;
    }

    socket_len = (socklen_t)sizeof(socket_addr);

    if ((accept_socket = accept(listen_socket, (struct sockaddr *)&socket_addr, &socket_len)) == -1) {
        close( listen_socket );
        perror("accept() failed");
        return 1;
    }
    server->gvim_socket = accept_socket;
    return 0;
}


static int process_gvim_event(vgdb_server *server)
{
    // 怎么做跳表, 这些以后再搞
    // <0 : 错误
    //  0 : gvim退出
    // >0 : 正常
    int         accept_socket;
    int         recv_len;
    int         mline_blen;
    int         mline_bnr;
    char       *mline_ptr;
    int         rwfd;
    static int  count;

    mline_ptr      = server->mline_buff;
    mline_blen     = server->mline_blen;
    mline_bnr      = server->mline_bnr;
    rwfd           = server->gdb_rwfd;
    accept_socket  = server->gvim_socket;
    count          = 0;

    recv_len = get_gvim_message(accept_socket, (void*)mline_ptr, mline_blen);
    if (recv_len == 0) {
        // 正常情况下这里不可能执行
        exec_gdb_cmd(server, "quit");
        close(server->gvim_socket);
        waitpid(server->gvim_pid, NULL, 0);
        server->gvim_state = VGDB_GVIM_QUIT;
        return 0;
    } else {
        count++;
        printf("gvim:%d=>%s\n", count, mline_ptr);
        if(!strcmp(mline_ptr, "0:disconnect=0")){
            exec_gdb_cmd(server, "quit");
            close(server->gvim_socket);
            waitpid(server->gvim_pid, NULL, 0);
            server->gvim_state = VGDB_GVIM_QUIT;
            return 0;
        }

        /*
        if(!strcmp()){
        }else if(!strcmp()){
        }else if(!strcmp()){
        }else if(!strcmp()){
        }else if(!strcmp()){
        }else if(!strcmp()){
        }else if(!strcmp()){
        }else if(!strcmp()){
        }else if(!strcmp()){
        }else if(!strcmp()){
        }
        */


    }
    return 1;
}

static int get_gvim_init_response(vgdb_server *server)
{
    // <0 : 错误
    //  0 : gvim退出
    // >0 : 正常
    int         accept_socket;
    int         recv_len;
    int         mline_blen;
    int         mline_bnr;
    char       *mline_ptr;
    int         rwfd;
    int         count;

    mline_ptr      = server->mline_buff;
    mline_blen     = server->mline_blen;
    mline_bnr      = server->mline_bnr;
    rwfd           = server->gdb_rwfd;
    accept_socket  = server->gvim_socket;
    count          = 1;

    while(1){

        recv_len = get_gvim_message(accept_socket, (void*)mline_ptr, mline_blen);
        // <0 : 关闭socket
        //  0 : 暂无
        // >0 : 数据长度
        if (recv_len == 0) {
            // 正常情况下这里不可能执行
            exec_gdb_cmd(server, "quit");
            close(server->gvim_socket);
            waitpid(server->gvim_pid, NULL, 0);
            server->gvim_state = VGDB_GVIM_QUIT;
            return 0;

        } else {
            if(!strcmp(mline_ptr, "0:startupDone=0")){
                server->gvim_state = VGDB_GVIM_RUN;
                break;
            }
        }
    }
    return 1;
}

static int set_gvim_first_file(vgdb_server *server)
{
    char            gcmd[128];
    char           *mline_ptr;
    char           *filename;
    int             mline_blen;
    int             offset;
    char            message[1024];

    mline_ptr  = server->mline_buff;
    mline_blen = server->mline_blen;
    filename   = server->current_file;

    strcpy(gcmd, "file ");
    strcat(gcmd, server->debug_file);
    exec_gdb_cmd(server, gcmd);

    exec_gdb_cmd(server, "list");
    exec_gdb_cmd(server, "info source");
    strcpy(filename, mline_ptr + mline_blen*3 + 11);

    exec_gdb_cmd(server, "b main");
    sscanf( strstr(mline_ptr + mline_blen*(server->mline_cols), ", line ") + 7, "%d", &(server->current_offset));

    exec_gdb_cmd(server, "clear main");

    sprintf(message, "0:setExitDelay!%d \"0\"", server->message_seqno);
    send_gvim_message(server->gvim_socket, message);
    server->message_seqno++;

    sprintf(message, "%d:editFile!%d \"%s\"", server->source_win_id, server->message_seqno, server->current_file);
    send_gvim_message(server->gvim_socket, message);
    server->message_seqno++;

    sprintf(message, "%d:startDocumentListen!%d", server->source_win_id, server->message_seqno);
    send_gvim_message(server->gvim_socket, message);
    server->message_seqno++;

    sprintf(message, "%d:setDot!%d %d/0", server->source_win_id, server->message_seqno, server->current_offset);
    send_gvim_message(server->gvim_socket, message);
    server->message_seqno++;

}

static int deliver_messages(vgdb_server *server)
{
    get_gdb_init_response(server);
    get_gvim_init_response(server);
    set_gvim_first_file(server);

    while(1){
        // 被动模式, server不会自发向vim发送消息
        if(!process_gvim_event(server)){
            break;
        }
    }
    return 0;
}


static int exec_gdb(vgdb_server* server)
{
    struct termios  stermios;

    if(tcgetattr(STDIN_FILENO,&stermios) >= 0) {
        stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
        stermios.c_oflag &= ~(ONLCR);
        tcsetattr(STDIN_FILENO,TCSANOW,&stermios);
    }
    execlp("gdb","gdb","--annotate=3",(char *) NULL);
}
static int exec_gvim(vgdb_server* server)
{
    execlp("vim", "vim", "-nb", (char*)NULL);
}


int vgdb_server_run(vgdb_server *server)
{

    server->gdb_pid     = -1;
    server->gvim_pid    = -1;

    server->gdb_rwfd    = -1;
    server->gvim_socket = -1;

    server->gvim_state  = VGDB_GVIM_STOP;
    server->gdb_state   = VGDB_GDB_STOP;
    server->state       = VGDB_STOP;

    server->mline_cols  = 0;
    server->mline_blen  = VGDB_MBUFF_LINE_LEN;
    server->mline_bnr   = VGDB_MBUFF_LINE_NR;

    server->file_name_len  = VGDB_FILE_NAME_LEN;
    server->current_offset = 0;

    server->source_win_id  = 1;
    server->message_seqno  = 1;

    pid_t           gvim_pid;
    pid_t           gdb_pid;
    int             gdb_rwfd;

    if ((gvim_pid = fork()) == 0) {
        exec_gvim(server);
    } else {
        server->gvim_pid = gvim_pid;
        connect_to_gvim(server);
        if ((gdb_pid = forkpty(&gdb_rwfd,NULL,NULL,NULL)) == 0) {
            exec_gdb(server);
        } else {
            server->gdb_pid  = gdb_pid;
            server->gdb_rwfd = gdb_rwfd;
            deliver_messages(server);
            //waitpid(server->gdb_pid, NULL, 0);
        }
        //waitpid(server->gvim_pid, NULL, 0);
    }
    return 0;
}
